#pragma once

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<cstdlib>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<pthread.h>
#include<unistd.h>
#include"Log.hpp"

#define BACKLOG 5

class TcpServer{

    //设置为单例模式
private:
    TcpServer(int port) : port_(port), listen_sock_(-1){}
    TcpServer(const TcpServer& svr) = delete;

public:

    static TcpServer* GetInstance(int port) {

        //相当于定义了一个全局锁（不用我们再去释放）
        static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
        
        if (nullptr == svr_) {
            pthread_mutex_lock(&lock);
            if (nullptr == svr_) {
                svr_ = new TcpServer(port);
                svr_->InitServer();
            }
            pthread_mutex_unlock(&lock);
        }
        return svr_;
    }
    void InitServer() {

        Socket();
        Bind();
        Listen();
    }

    void Socket() {

        listen_sock_ = socket(AF_INET, SOCK_STREAM, 0);
        //std::cout << listen_sock_ << std::endl;
        if (listen_sock_ < 0) {
            //创建监听套接字失败
            LOG(FATAL, "创建监听套接字失败");
            exit(1);
        }

        //设置可以被复用的套接字
        //SO_REUSEADDR是一个很有用的选项，一般服务器的监听socket都应该打开它。
        //它的大意是允许服务器bind一个地址，即使这个地址当前已经存在已建立的连接，比如：
        //1.服务器启动后，有客户端连接并已建立，如果服务器主动关闭，那么和客户端的连接会处于TIME_WAIT状态
        //此时再次启动服务器，就会bind不成功，报：Address already in use。
        //2.服务器父进程监听客户端，当和客户端建立链接后，fork一个子进程专门处理客户端的请求，如果父进程停止
        //因为子进程还和客户端有连接，所以再次启动父进程，也会报Address already in use。
        int opt = 1;
        setsockopt(listen_sock_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        LOG(INFO, "创建监听套接字成功！");
    }

    int Sock() {
        return listen_sock_;
    }

    void Bind() {

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        //设置协议族
        local.sin_family = AF_INET;
        //该端口号随着tcp相应是要发送（在网络上）给对方的，所以需要从主机序列转为网络序列
        local.sin_port = htons(port_);
        local.sin_addr.s_addr = INADDR_ANY;     //  云服务器不能直接绑定公网IP


        if (bind(listen_sock_, (sockaddr*)&local, sizeof(local)) < 0) {
            LOG(FATAL, "绑定监听套接字失败！");
            exit(2);
        }
        LOG(INFO, "绑定监听套接字成功！");
    }

    void Listen() {
        if (listen(listen_sock_, BACKLOG) < 0) {
            LOG(FATAL, "监听失败！");
            exit(3);
        }
        LOG(INFO, "监听成功！");
    }

    ~TcpServer() {
        if (listen_sock_ >= 0) {
            close(listen_sock_);
        }
    }
private:
    int port_;
    int listen_sock_;
    static TcpServer* svr_;
};

TcpServer* TcpServer::svr_ = nullptr;